﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using DrawToolsLib;

namespace MVVM_CAD.Model
{
    public abstract class FigureBase : DrawingVisual, IFigure
    {
        //public bool IsSelected;
        
        public IList<IPoint> PointsCollection { get; set; }

        public DrawStatus DrawStatus { get; set; }

        public string Name { get; set; }

        public IPoint InitialPoint
        {
            get { return PointsCollection[0]; }
        }

        public IPoint FinalPoint
        {
            get 
            { 
                return  PointsCollection.Count > 1? PointsCollection[PointsCollection.Count - 1]: InitialPoint; }
        }

        #region Graphics
        #region Class Members

        protected const double HitTestWidth = 8.0;
        protected const double HandleSize = 12.0;

        // Objects used to draw handles. I want to keep them all time program runs,
        // therefore I don't use pens, because actual pen size can change.

        // external rectangle
        static SolidColorBrush handleBrush1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
        // middle
        static SolidColorBrush handleBrush2 = new SolidColorBrush(Color.FromArgb(255, 255, 255, 255));
        // internal
        static SolidColorBrush handleBrush3 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));    

        #endregion Class Members

        #region Constructor
        
        protected FigureBase(string figureName)
        {
            Name = figureName;
            PointsCollection = new List<IPoint>();
            DrawStatus = DrawStatus.Incompleted;
            _objectId = this.GetHashCode();
        }

        #endregion Constructor


        #region Properties

        protected bool Selected;
        public bool IsSelected
        {
            get 
            { 
                return Selected; 
            }
            set 
            {
                Selected = value;

                RefreshDrawing();
            }
        }

        protected double GraphicsLineWidth;
        public double LineWidth
        {
            get
            {
                return GraphicsLineWidth;
            }

            set
            {
                GraphicsLineWidth = value;

                RefreshDrawing();
            }
        }

        protected Color GraphicsObjectColor;
        public Color ObjectColor
        {
            get
            {
                return GraphicsObjectColor;
            }

            set
            {
                GraphicsObjectColor = value;

                RefreshDrawing();
            }
        }

        protected double GraphicsActualScale;
        public double ActualScale
        {
            get
            {
                return GraphicsActualScale;
            }

            set
            {
                GraphicsActualScale = value;

                RefreshDrawing();
            }
        }

        protected double ActualLineWidth
        {
            get
            {
                return GraphicsActualScale <= 0 ? GraphicsLineWidth : GraphicsLineWidth / GraphicsActualScale;
            }
        }

        protected double LineHitTestWidth
        {
            get
            {
                // Ensure that hit test area is not too narrow
                return Math.Max(8.0, ActualLineWidth);
            }
        }

        // Allows to write Undo - Redo functions and don't care about
        // objects order in the list.
        int _objectId;
        public int Id
        {
            get { return _objectId; }
            set { _objectId = value; }
        }


        #endregion Properties

        #region Abstract Methods and Properties

        /// <summary>
        /// Returns number of handles
        /// </summary>
        public abstract int HandleCount
        {
            get;
        }


        /// <summary>
        /// Hit test, should be overwritten in derived classes.
        /// </summary>
        public abstract bool Contains(IPoint point);

        /// <summary>
        /// Create object for serialization
        /// </summary>
        //public abstract PropertiesGraphicsBase CreateSerializedObject();

        /// <summary>
        /// Get handle point by 1-based number
        /// </summary>
        public abstract IPoint GetHandle(int handleNumber);

        /// <summary>
        /// Hit test.
        /// Return value: -1 - no hit
        ///                0 - hit anywhere
        ///                > 1 - handle number
        /// </summary>
        //public abstract int MakeHitTest(Point point);


        /// <summary>
        /// Test whether object intersects with rectangle
        /// </summary>
        //public abstract bool IntersectsWith(Rect rectangle);

        /// <summary>
        /// Move object
        /// </summary>
        public abstract void Move(double deltaX, double deltaY);


        /// <summary>
        /// Move handle to the point
        /// </summary>
        public abstract void MoveHandleTo(IPoint point, int handleNumber);

        /// <summary>
        /// Get cursor for the handle
        /// </summary>
        public abstract Cursor GetHandleCursor(int handleNumber);

        #endregion Abstract Methods and Properties

        #region Virtual Methods

        /// <summary>
        /// Normalize object.
        /// Call this function in the end of object resizing,
        /// </summary>
        public virtual void Normalize()
        {
            // Empty implementation is OK for classes which don't require
            // normalization, like line.
            // Normalization is required for rectangle-based classes.
        }

        /// <summary>
        /// Implements actual drawing code.
        /// 
        /// Call GraphicsBase.Draw in the end of every derived class Draw 
        /// function to draw tracker if necessary.
        /// </summary>
        public virtual void Draw(DrawingContext drawingContext)
        {
            if ( IsSelected )
            {
                DrawTracker(drawingContext);
            }
        }


        /// <summary>
        /// Draw tracker for selected object.
        /// </summary>
        public virtual void DrawTracker(DrawingContext drawingContext)
        {
            for (int i = 1; i <= HandleCount; i++)
            {
                DrawTrackerRectangle(drawingContext, GetHandleRectangle(i));
            }
        }
        
        #endregion Virtual Methods

        #region Other Methods

        /// <summary>
        /// Draw tracker rectangle
        /// </summary>
        static void DrawTrackerRectangle(DrawingContext drawingContext, Rect rectangle)
        {
            // External rectangle
            drawingContext.DrawRectangle(handleBrush1, null, rectangle);

            // Middle
            drawingContext.DrawRectangle(handleBrush2, null, 
                new Rect(rectangle.Left + rectangle.Width/8, 
                         rectangle.Top + rectangle.Height/8,
                         rectangle.Width* 6 / 8,
                         rectangle.Height* 6 / 8));

            // Internal
            drawingContext.DrawRectangle(handleBrush3, null,
                new Rect(rectangle.Left + rectangle.Width / 4,
                 rectangle.Top + rectangle.Height / 4,
                 rectangle.Width / 2,
                 rectangle.Height / 2));
        }


        /// <summary>
        /// Refresh drawing.
        /// Called after change if any object property.
        /// </summary>
        public void RefreshDrawing()
        {
            DrawingContext dc = this.RenderOpen();

            Draw(dc);

            dc.Close();
        }

        /// <summary>
        /// Get handle rectangle by 1-based number
        /// </summary>
        public Rect GetHandleRectangle(int handleNumber)
        {
            IPoint point = GetHandle(handleNumber);

            // Handle rectangle should have constant size, except of the case
            // when line is too width.
            double size = Math.Max(HandleSize / GraphicsActualScale, ActualLineWidth * 1.1);

            return new Rect(point.X - size / 2, point.Y - size / 2,
                size, size);
        }

        /// <summary>
        /// Helper function used for Dump
        /// </summary>
        static string DoubleForDisplay(double value)
        {
            return ((float)value).ToString("f2", CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Helper function used for Dump
        /// </summary>
        static string ColorToDisplay(Color value)
        {
            //return "A:" + value.A.ToString() +
            return "R:" + value.R.ToString(CultureInfo.InvariantCulture) +
                   " G:" + value.G.ToString(CultureInfo.InvariantCulture) +
                   " B:" + value.B.ToString(CultureInfo.InvariantCulture);
        }


        #endregion Other Methods
        #endregion
    }
}
